package de.ifgi.tank;

import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;

/**
 * The main activity of the App. The features include (1) calculating the engine
 * values through acceleration measurements and the display slider (2) switching
 * the driving mode -- forward/reverse/spin (3) drawing an image of the tank and
 * updating it in real time depending on the current state of the real tank --
 * driving/standing still (4) switching warning lights on behind or in front of
 * the virtual tank, if there is something behind or in front of the real tank
 * 
 * @author d.wilhelm@wwu.de
 * @author g.pape@wwu.de
 * @author matthias.robbers@wwu.de
 * 
 */
public class MainActivity extends Activity implements SensorEventListener,
		OnTouchListener {

	// classes that send and receive UDP Packets
	private UDPSend udp;
	private UDPReceive udpReceive;

	// sensor manager used for the tilt
	private SensorManager sensorManager;
	private Sensor accelerometer;
	private Display display; // for the height of the display
	private View speed; // view on the right for speed

	private float power = 0;
	private boolean drive = false;
	private long lasttime = 0;

	// for the check if in right WiFi
	private WifiManager wifiManager;

	// reverse gear and spinning mode
	private boolean reverse = false;
	private boolean spin = false;
	private CheckBox reverseButton, spinButton;

	// live image of the tank
	private TankView tank;
	private Timer timer;
	private DriveTask driveTask;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		// tank image
		tank = (TankView) findViewById(R.id.panzer);

		// Initialize the sensor manager
		sensorManager = (SensorManager) this.getApplicationContext()
				.getSystemService(Context.SENSOR_SERVICE);
		accelerometer = sensorManager
				.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		sensorManager.registerListener(this, accelerometer,
				SensorManager.SENSOR_DELAY_NORMAL);

		// get the display
		WindowManager w = (WindowManager) getApplicationContext()
				.getSystemService(Context.WINDOW_SERVICE);
		display = w.getDefaultDisplay();

		speed = this.findViewById(R.id.speed);
		speed.setOnTouchListener(this);

		// initialize the WiFi manager
		wifiManager = (WifiManager) this.getApplicationContext()
				.getSystemService(Context.WIFI_SERVICE);

		// checkbox for reverse gear
		reverseButton = (CheckBox) findViewById(R.id.reverse);
		reverseButton.setOnCheckedChangeListener(new OnCheckedChangeListener() {

			@Override
			public void onCheckedChanged(CompoundButton buttonView,
					boolean isChecked) {
				if (isChecked) {
					reverse = true;
				} else {
					reverse = false;
				}
			}
		});

		// checkbox for spinning mode
		spinButton = (CheckBox) findViewById(R.id.spin);
		spinButton.setOnCheckedChangeListener(new OnCheckedChangeListener() {

			@Override
			public void onCheckedChanged(CompoundButton buttonView,
					boolean isChecked) {
				if (isChecked) {
					spin = true;
				} else {
					spin = false;
				}
			}
		});

		// set all warning lights to off
		updateWarnings(0);

		// start listening for packages from the Arduino
		udpReceive = new UDPReceive(this, 8000);
	}

	@Override
	protected void onPause() {
		super.onPause();
		sensorManager.unregisterListener(this);
	}

	@Override
	protected void onStop() {
		super.onStop();
		// stop listening
		if (udpReceive != null) {
			udpReceive.stop();
		}
	}

	@Override
	protected void onResume() {
		super.onResume();
		// device has to be connected to the right wifi, otherwise -> alert
		String SSID = wifiManager.getConnectionInfo().getSSID();
		if (SSID == null || !SSID.equals("MSP")) {
			createWrongWifiAlert();
		} else {
			try {
				udp = new UDPSend(InetAddress.getByName("192.168.1.130"), 7777);
			} catch (SocketException e) {
				e.printStackTrace();
			} catch (UnknownHostException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	}

	/**
	 * creates an AlertDialog which can lead the user to the WiFi settings
	 */
	private void createWrongWifiAlert() {
		new AlertDialog.Builder(this)
				.setMessage(
						"It seems your are in the wrong Wifi network. Want to go to the settings?")
				.setCancelable(false)
				.setPositiveButton("Go to settings",
						new DialogInterface.OnClickListener() {
							public void onClick(DialogInterface dialog, int id) {
								// go to the Location settings
								startActivity(new Intent(
										android.provider.Settings.ACTION_WIFI_SETTINGS));
							}
						})
				.setNegativeButton("No", new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int id) {
						finish();
					}
				}).show();
	}

	/**
	 * Maps the values linear into a defined range
	 * 
	 * @param x
	 *            , the value
	 * @param in_min
	 *            , minimum input
	 * @param in_max
	 *            , maximum input
	 * @param out_min
	 *            , minimum output
	 * @param out_max
	 *            , maximum output
	 */
	public float mapValue(float x, float in_min, float in_max, float out_min,
			float out_max) {
		return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
	}

	/**
	 * This method is called when a sensor registered a change. In our case the
	 * accelerometer. The tilt value is converted into left/right commands
	 */
	@Override
	public void onSensorChanged(SensorEvent event) {
		// Measure only every 100ms
		if (System.currentTimeMillis() - lasttime > 100 && this.drive) {
			lasttime = System.currentTimeMillis();
			float x = event.values[0];
			float y = event.values[1];

			double left = 0;
			double right = 0;

			// Checks the two landscape possibilities
			if (x < 0) {
				// Right turn
				if (y < 0) {
					left = 1023;
					right = 1023 - Math.abs(y) * 100;
				}
				// Left turn
				if (y > 0) {
					left = 1023 - Math.abs(y) * 100;
					right = 1023;
				}
			}
			if (x > 0) {
				// Right turn
				if (y > 0) {
					left = 1023;
					right = 1023 - Math.abs(y) * 100;
				}
				// Left turn
				if (y < 0) {
					left = 1023 - Math.abs(y) * 100;
					right = 1023;
				}
			}

			// Regulation of speed
			left = left * this.power;
			right = right * this.power;

			// if spinning mode enabled: equalize left and right values
			if (spin) {
				if (left >= right) {
					right = left;
				} else {
					left = right;
				}
			}

			// Sending udp package
			udp.sendCommand((int) left, (int) right, reverse, spin);
			Log.i("Values", (int) left + "; " + (int) right + " Power: "
					+ power + " Reverse: " + String.valueOf(reverse));
		}
	}

	/**
	 * This method is called when the user touches the view. In here the the
	 * value for the speed is generated depending on the position of the finger.
	 */
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		// if slider is touched
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			this.drive = true;
		}
		// slider released
		if (event.getAction() == MotionEvent.ACTION_UP) {
			this.drive = false;
			udp.sendCommand(0, 0, false, false);
			speed.setBackgroundColor(Color.rgb(255, 0, 0));
		} else {
			// receiving the power value
			power = getPower(event.getY());
			int bla = (int) mapValue(power, 0, 1, 0, 255);
			int red = 255 - bla;
			int green = 0 + bla;
			speed.setBackgroundColor(Color.rgb(red, green, 0));
		}
		return true;
	}

	/**
	 * Calculates the power value according to the normalized height of the
	 * display view
	 */
	public float getPower(float y) {
		float power = 1 - mapValue(y, 0, display.getHeight(), 0, 1);
		if (power < 0)
			return 0;
		if (power > 1)
			return 1;
		else {
			return power;
		}
	}

	/**
	 * this methods controls the warning lights of the tank image
	 */
	public void updateWarnings(int where) {
		tank.clearLights();
		switch (where) {
		case 1:
			tank.l1 = true;
			break;
		case 2:
			tank.l2 = true;
			break;
		case 3:
			tank.l3 = true;
			break;
		case 4:
			tank.l4 = true;
			break;
		case 5:
			tank.l5 = true;
			break;
		case 6:
			tank.l6 = true;
			break;
		}
		tank.invalidate();
	}

	/**
	 * starts the tank image animation
	 */
	public void startTankView() {
		if (timer == null) {
			driveTask = new DriveTask();
			timer = new Timer();
			timer.schedule(driveTask, 0, 150);
		}
	}

	/**
	 * stops the tank image animation
	 */
	public void stopTankView() {
		if (timer != null) {
			timer.cancel();
			timer = null;
		}
	}

	/**
	 * TimerTask to change the color of the tank's tracks that it looks like the
	 * tank is driving
	 * 
	 * @author matthias.robbers@wwu.de
	 * 
	 */
	class DriveTask extends TimerTask {
		@Override
		public void run() {
			handler.sendEmptyMessage(0);
		}

		private Handler handler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				super.handleMessage(msg);
				tank.drive();
				tank.invalidate();
			}
		};
	};

}